import { h, mergeProps, unref, isVNode } from 'vue';
import { get } from 'lodash-es';
import { Row, Col } from 'ant-design-vue';
import { NONE_TEXT } from '@gis/lib/config/index.js';

/* 坑!!! Number(null) = 0 */
function isSpan(span) {
  span = parseInt(span);
  return Number.isFinite(span) && 0 <= span && span <= 24;
}

function getSpan() {
  let args = [].slice.apply(arguments);
  for (let item of args) {
    if (isSpan(item)) return parseInt(item);
  }
}

function getColSpan(scope) {
  let { labelCol, wrapperCol } = scope || {};
  labelCol = parseInt(labelCol);
  wrapperCol = parseInt(wrapperCol);
  if (!isSpan(labelCol) && !isSpan(wrapperCol)) {
    return null;
  }
  return {
    label: isSpan(labelCol) ? labelCol : 24 - (isSpan(wrapperCol) ? wrapperCol : 0) || 24,
    content: isSpan(wrapperCol) ? wrapperCol : 24 - (isSpan(labelCol) ? labelCol : 0) || 24,
  };
}

function getValue(val, defaultValue) {
  return val === 0 || !!val ? val : defaultValue || '';
}

function getScopeValue(scope = {}, useDefault) {
  let { dataSource, key, field, format, noneText } = scope;
  let valField = key || field;
  let fields = valField && typeof valField === 'string' ? valField.match(/({[^{}]+})/g) : null;
  let dftVal = useDefault !== false ? noneText || NONE_TEXT : '';
  if (typeof format !== 'function') format = (v) => v;
  if (Array.isArray(fields) && fields.length) {
    return fields.reduce((res, item) => {
      let k = item.replace(/[{}\s]/g, '');
      let v = getValue(format(get(dataSource, k), dataSource, k), dftVal);
      return res.replace(new RegExp(item, 'g'), v);
    }, valField);
  }
  return getValue(format(get(dataSource, valField), dataSource, valField), dftVal);
}

const baseStyle = { color: 'inherit', lineHeight: 'inherit' };

const Description = {
  name: 'Description',
  props: {
    dataSource: { type: Object, default: () => ({}) },
    label: { type: String, default: undefined },
    value: { default: undefined },
    text: { default: undefined },
    field: { type: String, default: undefined },
    semi: { type: [String, Boolean], default: undefined },
    format: { type: Function, default: undefined },
    noneText: { type: String, default: undefined },
    labelCol: { type: [Number, String], default: undefined },
    wrapperCol: { type: [Number, String], default: undefined },
    labelStyle: { type: [String, Object], default: undefined },
    wrapperStyle: { type: [String, Object], default: undefined },
    customLabel: { type: Function, default: undefined },
    customContent: { type: Function, default: undefined },
  },
  computed: {
    _value() {
      return this.$props.value || getScopeValue(this.$props, false);
    },
    _text() {
      return this.$props.text || getScopeValue(this.$props, true);
    },
    itemCol() {
      return getColSpan(this.$props);
    },
    itemStyle() {
      return {
        label: Object.assign({ ...baseStyle, opacity: 0.5 }, this.$props.labelStyle),
        content: Object.assign({ ...baseStyle, opacity: 1 }, this.$props.wrapperStyle),
      };
    },
  },
  methods: {
    customRender(renderer, props) {
      let _props = Object.assign({ ...this.$props, value: this._value, text: this._text, h }, props);
      if (typeof renderer === 'function') {
        return renderer(_props);
      }
      return isVNode(renderer) ? h(renderer, _props) : null;
    },
    renderSlot(name, props) {
      let _props = Object.assign({ ...this.$props, value: this._value, text: this._text }, props);
      if (typeof this.$slots[name] === 'function') return this.$slots[name](_props);
    },
    renderText() {
      let children = null;
      let getText = (v) => (['string', 'number'].includes(typeof v) ? v : null);
      if (isVNode(this._text)) {
        children = () => h(this._text);
      }
      if (Array.isArray(this._text)) {
        children = this._text.map((item) => {
          return isVNode(item) ? h(item) : getText(item);
        });
      }
      return h('span', children || getText(this._text));
    },
    renderContent() {
      return this.customRender(this.customContent) || this.renderSlot('default') || this.renderText();
    },
    renderLabel() {
      let label = this.$props.label;
      return [
        this.customRender(this.customLabel) || this.renderSlot('label') || h('span', label),
        this.$props.semi !== false && this.$props.semi !== null ? h('span', '：') : null,
      ];
    },
    renderCol(type, children) {
      let tag = this.itemCol ? Col : 'span';
      let span = this.itemCol ? this.itemCol[type] : null;
      let slots = this.itemCol ? () => children : children;
      return h(tag, { span, style: this.itemStyle[type] }, slots);
    },
    renderRow() {
      let tag = this.itemCol ? Row : 'div';
      let baseProps = this.itemCol ? { type: 'flex', gutter: 0 } : {};
      return h(tag, { ...baseProps, style: { ...baseStyle } }, [
        this.renderCol('label', this.renderLabel()),
        this.renderCol('content', this.renderContent()),
      ]);
    },
  },
  render() {
    return this.renderRow();
  },
};

const Descriptions = {
  name: 'Descriptions',
  props: {
    /*
     * columns: [
     *   {
     *     customRender: null,
     *     customLabel: null,
     *     customContent: null,
     *     key: "", or field: "",
     *     label: "", or title: "",
     *     colSpan: 12,
     *     labelCol: 6,
     *     wrapperCol: 18,
     *     style: {},
     *     labelStyle: {},
     *     wrapperStyle: {},
     *     format: (v) => v,
     *     visible: Boolean Or () => Boolean,
     *     children: []
     *     itemSpan: 12,
     *   }
     * ] */
    columns: { type: Array, default: () => [] },
    dataSource: { type: Object, default: () => ({}) },
    gutter: { type: [Number, String], default: 6 },
    colSpan: { type: [Number, String], default: 12 },
    ...Description.props,
  },
  computed: {
    space() {
      let gap = /^\d+$/.test(this.gutter) ? parseInt(this.gutter) : null;
      return gap ? (gap || 6) + 'px' : this.gutter || '6px';
    },
  },
  methods: {
    getField(item) {
      return item.key || item.field;
    },
    getVisible(item) {
      let field = this.getField(item);
      let visible = item.visible;
      if (typeof visible === 'function') {
        visible = visible(get(this.dataSource, field), this.dataSource, item);
      }
      return unref(visible) !== false;
    },
    getSlotAttrs(item) {
      let field = this.getField(item);
      return Object.assign({}, this.$props, item, {
        key: field,
        field: field,
        columns: null,
        dataSource: this.dataSource,
      });
    },
    renderItem(item) {
      if (item.children && item.children.length) {
        return this.renderRow(item.children, { style: { height: '100%' } }, item);
      }
      let field = this.getField(item);
      let scope = this.getSlotAttrs(item);
      let label = scope.label || scope.title;
      let text = getScopeValue(scope, true);
      let value = getScopeValue(scope, false);
      let title = typeof value === 'string' ? value : '';
      let _props = { label, value, text, title, dataSource: this.dataSource };
      if (!value && label) {
        return h(Description, { ...scope, ..._props }, this.$slots);
      }
      if (this.$slots[field]) {
        return this.$slots[field]({ scope, ..._props });
      }
      if (item.customRender) {
        let props = { h, scope, ..._props };
        return typeof item.customRender === 'function' ? item.customRender(props) : h(item.customRender, props);
      }
      return h(Description, { ...scope, ..._props }, this.$slots);
    },
    renderCol(item) {
      let key = this.getField(item);
      let span = getSpan(item.colSpan, this.colSpan);
      let style = {
        padding: !item.isChild ? this.space : '',
        whiteSpace: 'normal',
        wordWrap: 'break-word',
        wordBreak: 'break-all',
        ...baseStyle,
      };
      return h(Col, { key, span, style: Object.assign(style, item.style) }, () => {
        return this.renderItem(item);
      });
    },
    renderRow(columns, _props, parent) {
      if (!columns || !columns.length) {
        return null;
      }
      return h(Row, mergeProps({ type: 'flex', style: { ...baseStyle, display: 'flex' } }, _props), () => {
        return columns.reduce((res, item) => {
          if (item && typeof item === 'object' && this.getVisible(item)) {
            let colSpan = getSpan(item.colSpan, parent ? parent.itemSpan : null);
            res.push(this.renderCol(Object.assign({}, item, { colSpan, isChild: !!parent })));
          }
          return res;
        }, []);
      });
    },
  },
  render() {
    return h('div', { style: { lineHeight: 1.75 } }, this.renderRow(this.columns));
  },
};

Descriptions.Description = Description;

export default Descriptions;
